
import { BadRequestException, Injectable } from '@nestjs/common'
import * as fs from 'fs'
import { resolve } from 'path'
import { VIDEO_PATH, CHUNK_PATH, SEND_VIDEO_PATH } from '../utils/constant'
import { pipeline } from 'stream'

@Injectable()
export class UploadService {
  // 上传图片
  uploadImg(files, sort) {
    if (files == undefined) {
      throw new BadRequestException('请选择需要上传的文件')
    } else {
      // 返回文件的名字
      return `/uploads/${sort}/` + files?.filename
    }
  }

  // 检查md5文件是否存在======若存在则秒传||不存在就断点续传
  async checkFileExist(query) {
    const md5 = query.md5
    const name = query.fileName

    // 判断该文件夹是否存在======若存在则秒传
    const is_quick_upload = this.isExist(`${VIDEO_PATH}/${name}/${md5}.mp4`)

    // 如果不存在则返回对应的chunks列表======断点续传
    if (!is_quick_upload) {
      let chunkList = []

      // 判断是否存在chunks文件,不存在则创建
      if (!this.isExist(CHUNK_PATH)) {
        this.isExist(CHUNK_PATH)
      }

      // 判断是否存在对应的md5文件
      const md5_exist = this.isExist(`${CHUNK_PATH}/${md5}`)

      // 如果存在======断点续传
      if (md5_exist) {
        //  读取该目录下的所有文件
        const all_chunks = (await this.listDir(`${CHUNK_PATH}/${md5}`)) as any[]
        // 除去后缀名,返回索引
        chunkList = all_chunks.map(item => {
          return Number(item)
        })
      }

      return {
        is_exist: false,
        chunkList
      }
    } else {
      return {
        is_exist: true,
        path: `${SEND_VIDEO_PATH}/${name}/${md5}.mp4`,
        desc: '秒传'
      }
    }
  }

  // 分片上传====如果已经上传过部分,则为断点续传
  async uploadChunk(file, body) {
    let { index, md5, total } = body
    total = Number(total)

    // 如果md5不存在则创建目录====一般是第一次上传使用
    if (!this.isExist(`${CHUNK_PATH}/${md5}`)) {
      fs.mkdirSync(`${CHUNK_PATH}/${md5}`)
    }

    // 将blob写入文件,文件名为索引名字  通过promise使得异步按顺序执行
    await this.writeStream(`${CHUNK_PATH}/${md5}/${index}`, file.buffer)

    // 判断是否全部上传完毕
    const chunk_total = ((await this.listDir(`${CHUNK_PATH}/${md5}`)) as any[]).length

    return { desc: '上传成功', rest: total - chunk_total }
  }

  // 合并分片======分片上传完后
  async mergeChunk(query) {
    const { md5, fileName, total, chunkSize } = query

    // 列出所有的chunk文件
    const all_chunks = (await this.listDir(`${CHUNK_PATH}/${md5}`)) as any[]

    // 索引从小到大排列
    all_chunks.sort((a, b) => a - b)

    // 如果后端切片的总数和前端切片的总数不一样
    if (Number(total) !== all_chunks.length) {
      return '合成失败,分片数量不一致'
    }

    // 不存在该文件则创建-----以视频名字取的
    if (!this.isExist(`${VIDEO_PATH}/${fileName}`)) {
      fs.mkdirSync(`${VIDEO_PATH}/${fileName}`)
    }

    // // 放入promise中一起执行=====？？？视频合成有时候播放不了
    // Promise.all(
    //   all_chunks.map(item => {
    //     // 开始写的位置
    //     const start = item * chunkSize
    //     console.log(start)
    //     // 使用管道连接读流和写流
    //     return new Promise((resolve, reject) => {
    //       pipeline(
    //         fs.createReadStream(`${CHUNK_PATH}/${md5}/${item}`),
    //         fs.createWriteStream(`${VIDEO_PATH}/${md5}`, {
    //           flags: 'w+',
    //           start: start,
    //           autoClose: true
    //         }),
    //         err => {
    //           if (!err) {
    //             resolve('success')
    //           }
    //           console.log(err)
    //         }
    //       )
    //     })
    //   })
    // ).then(() => {
    //   fs.rename(`${VIDEO_PATH}/${md5}`, `${VIDEO_PATH}/${md5}.mp4`, () => {})
    // })

    // 递归合并切片
    this.mergeChunks(all_chunks, fs.createWriteStream(`${VIDEO_PATH}/${fileName}/${md5}.mp4`, { flags: 'w+' }), `${CHUNK_PATH}/${md5}`)

    return {
      path: `${SEND_VIDEO_PATH}/${fileName}/${md5}.mp4`
    }
  }

  // 递归合成切片
  async mergeChunks(chunk_list = [], fileWriteStream, chunks_path) {
    // 如果数组长度为空则切片合成完毕
    if (!chunk_list.length) {
      // 列出所有的chunk文件
      const all_chunks = (await this.listDir(`${chunks_path}`)) as any[]
      // 把分片删除=====???删除不掉，报错
      // await this.deleteChunks(all_chunks, chunks_path)

      return fileWriteStream.end('chunks finish')
    }
    // 创建可读流
    const read_stream = fs.createReadStream(`${chunks_path}/${chunk_list.shift()}`)

    // 一直打开可写流===直到合并完再关闭
    read_stream.pipe(fileWriteStream, { end: false })

    // 监听可读流的end事件,读完后读下一个分片======注意需要使用箭头函数来获取上层的this
    read_stream.on('end', () => {
      this.mergeChunks(chunk_list, fileWriteStream, chunks_path)
    })

    // 监听错误事件====并且关闭可写流，防止内存泄露
    read_stream.on('error', function (error) {
      console.log(error)
      fileWriteStream.close()
    })
  }

  // 删除分片文件
  async deleteChunks(chunk_list, chunks_path) {
    // 删除文件
    for (let i = 0; i < chunk_list.length; i++) {
      fs.unlinkSync(`${chunks_path}/${chunk_list[i]}`)
    }
    fs.rmdirSync(chunks_path)
  }

  // 流写入
  writeStream(path, file) {
    // 将blob写入文件,文件名为索引名字
    return new Promise((resolve, reject) => {
      const ws = fs.createWriteStream(path, {
        flags: 'w',
        autoClose: true
      })

      ws.write(file, () => {
        resolve('success')
      })
    })
  }

  // 列出文件夹下的所有文件
  listDir(path) {
    return new Promise((resolve, reject) => {
      fs.readdir(path, (err, files) => {
        resolve(files)
      })
    })
  }

  // 检查文件是否存在
  isExist(path) {
    return fs.existsSync(path)
  }
}
